package cn.flant.exec;

import java.util.concurrent.TimeUnit;

public final class SyncExecutor {

	private SyncExecutor() {
	}

	public static void execute(Runnable command, long timeout) throws TimeoutException, ExecuteException {
		if (command == null) {
			throw new NullPointerException("command can not be null");
		}
		if (timeout <= 0) {
			timeout = Integer.MAX_VALUE;
		}
		Executor executor = new Executor(command);
		executor.start();
		long timeRemaind = timeout;
		while (!executor.isDone()) {
			if (timeRemaind <= 0) {
				executor.interrupt();
				throw new TimeoutException(executor, command, timeout);
			}
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				executor.interrupt();
				break;
			}
			timeRemaind -= 50;
		}
		if (executor.getException() != null) {
			throw new ExecuteException("an exception occurs when execute command", executor.getException());
		}
	}

	public static void execute(Runnable command, long timeout, TimeUnit unit) throws TimeoutException, ExecuteException {
		execute(command, TimeUnit.MILLISECONDS.convert(timeout, unit));
	}

	public static void execute(Runnable command) throws ExecuteException {
		try {
			execute(command, Integer.MAX_VALUE);
		} catch (TimeoutException e) {
		}
	}

	static class Executor extends Thread {

		private boolean isDone = false;
		private Runnable command = null;
		private Throwable exception = null;

		public Executor(Runnable command) {
			this.command = command;
		}

		public void run() {
			try {
				command.run();
			} catch (Throwable e) {
				exception = e;
			} finally {
				isDone = true;
			}
		}

		public boolean isDone() {
			return isDone;
		}

		public Throwable getException() {
			return exception;
		}
	}

	public static class TimeoutException extends Exception {
		private static final long serialVersionUID = 3978917308617939828L;

		private Thread executor = null;
		private Runnable command = null;
		private long timeout = -1;

		protected TimeoutException(Thread executor, Runnable command, long timeout) {
			super("execute timeout in " + timeout + "ms");
			this.executor = executor;
			this.command = command;
			this.timeout = timeout;
		}

		public Thread getExecuteThread() {
			return executor;
		}

		public Runnable getCommand() {
			return command;
		}

		public long getTimeout() {
			return timeout;
		}
	}

	public static class ExecuteException extends RuntimeException {

		private static final long serialVersionUID = 5357264398282147816L;

		public ExecuteException() {
			super();
		}

		public ExecuteException(String message) {
			super(message);
		}

		public ExecuteException(String message, Throwable cause) {
			super(message, cause);
		}

		public ExecuteException(Throwable cause) {
			super(cause);
		}
	}
}
